package com.example.ad.sdk.domain.view.mvi.viewmodel

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.ad.sdk.domain.view.mvi.intent.IUIEffect
import com.example.ad.sdk.domain.view.mvi.intent.IUiIntent
import com.example.ad.sdk.domain.view.mvi.intent.IUiState
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch

/**
 * abstract BaseViewModel, viewModel继承与此类，需要定义IUIIntent\IUIState\IUIEffect, 可约束和简化 View -> Intent -> ViewModel -> State\Effect -> View
 * [I: Intent] IUiIntent(Action) 为了防止和Android的Intent()混淆
 * [M: State\Effect] 用于描述View的显示数据和状态, Effect用于描述SnackBar Toast Navigation 这些（热）事件
 * [IUIState: stateFlow] 需要默认初始化使用ShareFlow默认状态为null和LiveData一致通过下面方法转换
 * ``` kotlin
 * val stateFlow by lazy { _state.stateIn(viewModelScope, WhileSubscribed(), initial) }
 * ```
 * uiState聚合页面的全部UI状态的LiveData
 * ``` kotlin
 * val stateLiveData by lazy { state.asLiveData() }
 * ```
 */
abstract class BaseMVIViewModel<I: IUiIntent, S: IUiState, E: IUIEffect> : ViewModel() {
    /**
     * 定义一个私有的意图动作通道，用于在协程之间安全地传递意图动作
     * [_intentAction]包含用户与ui的交互（如点击操作），也有来自后台的消息（如切换自习模式）
     */
    private val _intentAction = Channel<I>()

    /**
     * [sendAction] 用于在非viewModelScope外使用
     */
    val sendAction: SendChannel<I> by lazy { _intentAction }

    /**
     * [_state] 聚合页面的全部UI状态
     * 不需要默认值不使用 StateFlow:https://github.com/Kotlin/kotlinx.coroutines/issues/2515
     */
    private val _state = MutableSharedFlow<S>(
        replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    //该属性使用 Flow 类型来管理 UI 状态或数据流
    //_state.distinctUntilChanged() 用于过滤掉相同状态的连续发射，只在状态实际改变时触发更新
    val state: Flow<S> by lazy { _state.distinctUntilChanged() }

    /**
     * [replayState] 重放当前uiState,replay始终是1
     */
    val replayState
        get() = _state.replayCache.firstOrNull()

    /**
     * [effect]事件带来的副作用，通常是一次性事件 例如：弹Toast、导航Fragment等
     * 声明一个懒加载的SharedFlow类型的属性effect
     * 它是通过将内部的_effect（一个Flow类型）转换为SharedFlow类型来实现的
     * 这种转换使得effect可以在多个协程之间安全共享，且只会被消费一次
     */
    private val _effect = MutableSharedFlow<E>()
    val effect: SharedFlow<E> by lazy {
        try {
            _effect.asSharedFlow()
        } catch (e: Exception) {
            // 这里可以根据实际场景处理异常，例如记录日志或抛出自定义异常
            throw RuntimeException("Failed to create SharedFlow", e)
        }
    }

    init {
        // 在ViewModel的生命周期范围内异步启动一个协程
        viewModelScope.launch {
            // 消耗并收集Intent动作流，直到流结束
            _intentAction.consumeAsFlow().collect {
                // 当收集到新的Intent动作时调用处理函数
                onIntentAction(it, replayState)
            }
        }
    }

    /**
     * 发送意图动作
     * 此函数用于在ViewModel的上下文中异步发送一个意图动作该函数通过
     * [viewModelScope.launch] 方法启动一个新的协程，并将指定的意图动作
     * 通过 [_intentAction.send] 方法发送出去
     *
     * @param action 要发送的意图动作，类型为 I
     * 表示任何实现了特定接口或类的动作对象
     */
    fun sendIntentAction(action: I) = viewModelScope.launch {
        _intentAction.send(action)
    }

    /**
     * 处理Intent动作的抽象方法
     *
     * 此方法旨在被子类实现，用于处理特定的Intent动作当一个Intent传递给当前状态机时，
     * 该方法将被调用以相应于Intent中指定的动作进行处理这是状态机模式的一个关键部分，
     * 允许系统根据外部Intent动态改变状态或执行特定操作
     *
     * @param action Intent中的动作信息，包含了需要执行的操作类型
     * @param currentState 当前的状态机状态，可能为空如果状态机尚未初始化或状态不适用，则为null
     */
    protected abstract fun onIntentAction(action: I, currentState: S?)

    /**
     * 发送状态变化事件
     *
     * 此函数用于将状态对象发送到观察者。它通过调用一个挂起函数来获取状态对象，
     * 并使用ViewModelScope来启动一个协程。如果builder返回一个非空的状态对象，
     * 则使用_channelState.emit来发送这个状态对象。
     *
     * @param builder 一个挂起函数，用于生成一个状态对象S。这个函数可以是耗时的，
     *                 因此使用了挂起函数的形式，允许在协程中调用。
     */
    protected fun emitState(builder: suspend () -> S?) = viewModelScope.launch {
        builder()?.let { _state.emit(it) }
    }

    /**
     * 发送效果事件
     *
     * 该函数用于在协程作用域内异步构建并发送一个效果事件效果事件可以是任意类型E的实例
     * 使用viewModelScope.launch启动一个新的协程，以便在UI线程以外的线程执行耗时操作
     * 如果builder构建出非空的效果事件，则通过_effect.emit(it)将其传递给_effect通道
     *
     * @param builder 悬挂函数，返回一个可能为null的效果事件E
     */
    protected fun emitEffect(builder: suspend () -> E?) = viewModelScope.launch {
        builder()?.let { _effect.emit(it) }
    }

    /**
     * 发送状态更新事件
     *
     * 此函数负责向监听器发送新的状态更新事件它将状态参数封装成一个事件，
     * 并将其发送到内部的状态通道中这样，任何订阅了该通道的监听器都会接收到状态更新的通知
     *
     * @param state 要发送的新状态
     */
    protected suspend fun emitState(state: S) = _state.emit(state)

    /**
     * 发送效果事件
     *
     * 此函数用于将效果事件（Effect）发送给观察者（Observer）或其他订阅者这个函数是suspend（挂起）函数，
     * 表明它只能在协程（Coroutine）上下文中调用发送效果的实现是通过调用_effect.emit函数来完成的，
     * 其中_effect是一个通道（Channel）或者类似可以承载事件发送的机制
     *
     * @param effect 要发送的效果事件，类型为E，E是具体效果类型的泛型占位符
     */
    protected suspend fun emitEffect(effect: E) = _effect.emit(effect)

    /** 不挂起发送 state ，返回 boolean */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    fun tryEmitState(state: S) = _state.tryEmit(state)

    /**不挂起发送 effect ，返回 ChannelResult */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    fun tryEmitEffect(effect: E) = _effect.tryEmit(effect)
}